/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package TimeRecording.tables;

import TimeRecording.business.DaisyChainSystem;
import TimeRecording.business.DaisyChainUtilities;
import TimeRecording.business.DateRange;
import TimeRecording.enums.HolidayRequestStatus;
import TimeRecording.enums.PartOfDay;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Query;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

/**
 *
 * @author david
 */
@Entity
@Table(name = "holiday_request")
@NamedQueries({
    @NamedQuery(
            name = "HolidayRequest.findAll",
            query = "SELECT h " +
                    "FROM HolidayRequest h"),
    @NamedQuery(
            name = "HolidayRequest.findByRequestId",
            query = "SELECT h " +
                    "FROM HolidayRequest h " +
                    "WHERE h.requestId = :requestId"),
    @NamedQuery(
            name = "HolidayRequest.findByFirstDayOfHoliday",
            query = "SELECT h " +
                    "FROM HolidayRequest h " +
                    "WHERE h.firstDayOfHoliday = :firstDayOfHoliday"),
    @NamedQuery(
            name = "HolidayRequest.findByLastDayOfHoliday",
            query = "SELECT h " +
                    "FROM HolidayRequest h " +
                    "WHERE h.lastDayOfHoliday = :lastDayOfHoliday"),
    @NamedQuery(
            name = "HolidayRequest.findByStatus",
            query = "SELECT h " +
                    "FROM HolidayRequest h " +
                    "WHERE h.status = :status"),
    @NamedQuery(
            name = "HolidayRequest.findByDateRange",
            query = "SELECT h " +
                    "FROM HolidayRequest h " +
                    "WHERE h.firstDayOfHoliday <= :lastDayOfHoliday AND " +
                          "h.lastDayOfHoliday >= :firstDayOfHoliday"),
    @NamedQuery(
            name = "HolidayRequest.findByEmployeeAndDateRange",
            query = "SELECT h " +
                    "FROM HolidayRequest h " +
                    "WHERE h.employeeId = :employeeId AND " +
                          "h.firstDayOfHoliday <= :lastDayOfHoliday AND " +
                          "h.lastDayOfHoliday >= :firstDayOfHoliday")})

public class HolidayRequest implements Serializable, Cloneable {

    public final static String BASE_QUERY = "SELECT h FROM HolidayRequest h";

    public final static String EMPLOYEE_FILTER = "h.employeeId = :employeeId";
    public final static String STATUS_FILTER = "h.status = :status";
    public final static String DATE_FILTER = "(h.firstDayOfHoliday <= :lastDayOfHoliday AND h.lastDayOfHoliday >= :firstDayOfHoliday)";

    public final static String COL_EMPLOYEE_ID = "employeeId";
    public final static String COL_STATUS = "status";
    public final static String COL_LOWER_DATE = "firstDayOfHoliday";
    public final static String COL_UPPER_DATE = "lastDayOfHoliday";

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "request_id")
    private Integer requestId;

    @Column(name = "first_day")
    @Temporal(TemporalType.DATE)
    private Date firstDayOfHoliday;

    @Column(name = "last_day")
    @Temporal(TemporalType.DATE)
    private Date lastDayOfHoliday;

    @Column(name = "taken_from_flexi")
    private Boolean takenFromFlexi;

    @Column(name = "hours_spent")
    private Double hoursSpent;

    @Column(name = "employee_comment")
    private String employeeComment;

    @Column(name = "administrator_comment")
    private String administratorComment;

    @Column(name = "status")
    private HolidayRequestStatus status;

    @Column(name = "first_day_part")
    private PartOfDay firstDayPart;

    @Column(name = "last_day_part")
    private PartOfDay lastDayPart;

    @JoinColumn(name = "employee_id", referencedColumnName = "employee_id")
    @ManyToOne(optional = false)
    private Employee employeeId;
    
    @JoinColumn(name = "administrator_id", referencedColumnName = "employee_id")
    @ManyToOne
    private Employee administratorId;

    // <editor-fold defaultstate="collapsed" desc="Property Change Support">
    @Transient
    private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);

    public void addPropertyChangeListener(PropertyChangeListener listener) {
	changeSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
	changeSupport.removePropertyChangeListener(listener);
    } // </editor-fold>

    public HolidayRequest() {
        firstDayPart = PartOfDay.ALL_DAY;
        lastDayPart = PartOfDay.ALL_DAY;
        employeeComment = "";
        administratorComment = "";
        hoursSpent = 0.0;
        takenFromFlexi = false;
        status = HolidayRequestStatus.PREPARATION;
    }

    public HolidayRequest(Integer requestId) {
        this();
        this.requestId = requestId;
    }

    public Integer getRequestId() {
        return requestId;
    }

    public void setRequestId(Integer requestId) {
	Integer oldrequestId = this.requestId;
	this.requestId = requestId;
	changeSupport.firePropertyChange("requestId", oldrequestId, requestId);
    }

    public Date getFirstDayOfHoliday() {
        return firstDayOfHoliday;
    }

    public void setFirstDayOfHoliday(Date firstDayOfHoliday) {
	Date oldfirstDayOfHoliday = this.firstDayOfHoliday;
	this.firstDayOfHoliday = firstDayOfHoliday;
	changeSupport.firePropertyChange("firstDayOfHoliday", oldfirstDayOfHoliday, firstDayOfHoliday);
    }

    public Date getLastDayOfHoliday() {
        return lastDayOfHoliday;
    }

    public void setLastDayOfHoliday(Date lastDayOfHoliday) {
	Date oldlastDayOfHoliday = this.lastDayOfHoliday;
	this.lastDayOfHoliday = lastDayOfHoliday;
	changeSupport.firePropertyChange("lastDayOfHoliday", oldlastDayOfHoliday, lastDayOfHoliday);
    }

    public Boolean getTakenFromFlexi() {
        return takenFromFlexi;
    }

    public void setTakenFromFlexi(Boolean takenFromFlexi) {
	Boolean oldtakenFromFlexi = this.takenFromFlexi;
	this.takenFromFlexi = takenFromFlexi;
	changeSupport.firePropertyChange("takenFromFlexi", oldtakenFromFlexi, takenFromFlexi);
    }

    public Double getHoursSpent() {
        return hoursSpent;
    }

    public void setHoursSpent(Double hoursSpent) {
	Double oldHoursSpent = this.hoursSpent;
	this.hoursSpent = hoursSpent;
	changeSupport.firePropertyChange("hoursSpent", oldHoursSpent, hoursSpent);
    }

    public String getEmployeeComment() {
        return employeeComment;
    }

    public void setEmployeeComment(String employeeComment) {
	String oldemployeeComment = this.employeeComment;
	this.employeeComment = employeeComment;
	changeSupport.firePropertyChange("employeeComment", oldemployeeComment, employeeComment);
    }

    public String getAdministratorComment() {
        return administratorComment;
    }

    public void setAdministratorComment(String administratorComment) {
	String oldadministratorComment = this.administratorComment;
	this.administratorComment = administratorComment;
	changeSupport.firePropertyChange("administratorComment", oldadministratorComment, administratorComment);
    }

    public HolidayRequestStatus getStatus() {
        return status;
    }

    public void setStatus(HolidayRequestStatus status) {
	HolidayRequestStatus oldstatus = this.status;
	this.status = status;
	changeSupport.firePropertyChange("status", oldstatus, status);
    }

    public PartOfDay getFirstDayPart() {
        return firstDayPart;
    }

    public void setFirstDayPart(PartOfDay firstDayPart) {
	PartOfDay oldFirstDayPart = this.firstDayPart;
	this.firstDayPart = firstDayPart;
	changeSupport.firePropertyChange("firstDayPart", oldFirstDayPart, firstDayPart);
    }

    public PartOfDay getLastDayPart() {
        return lastDayPart;
    }

    public void setLastDayPart(PartOfDay lastDayPart) {
	PartOfDay oldLastDayPart = this.lastDayPart;
	this.lastDayPart = lastDayPart;
	changeSupport.firePropertyChange("lastDayPart", oldLastDayPart, lastDayPart);
    }

    public Employee getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(Employee employeeId) {
	Employee oldemployeeId = this.employeeId;
	this.employeeId = employeeId;
	changeSupport.firePropertyChange("employeeId", oldemployeeId, employeeId);
    }

    public Employee getAdministratorId() {
        return administratorId;
    }

    public void setAdministratorId(Employee administratorId) {
	Employee oldadministratorId = this.administratorId;
	this.administratorId = administratorId;
	changeSupport.firePropertyChange("administratorId", oldadministratorId, administratorId);
    }

    public PartOfDay isActive(Date date) {

        // If the supplied date is before the holiday starts then bail out
        if (firstDayOfHoliday != null && date.before(firstDayOfHoliday)) {
            return null;
        }

        // If the supplied date is after the holiday end then bail out
        if (lastDayOfHoliday != null && date.after(lastDayOfHoliday)) {
            return null;
        }

        // If the supplied date falls on the first day of the holiday then
        // return whatever the request states is the required time

        if (firstDayOfHoliday != null && date.equals(firstDayOfHoliday)) {
            return firstDayPart;
        }

        // If the supplied date falls on the last day of the holiday then
        // return whatever the request states is the required time

        if (lastDayOfHoliday != null && date.equals(lastDayOfHoliday)) {
            return lastDayPart;
        }

        return PartOfDay.ALL_DAY;
    }

    public boolean isApproved() {
        return status != null &&
                status != HolidayRequestStatus.PREPARATION &&
                status != HolidayRequestStatus.SUBMITTED &&
                status != HolidayRequestStatus.REJECTED;
    }

    public static Query getHolidaysForEmployeeAndDateRange(
            EntityManager entityManager, Employee employee, DateRange dateRange) {

        return entityManager.createNamedQuery(
                "HolidayRequest.findByEmployeeAndDateRange").
                setParameter(COL_EMPLOYEE_ID, employee).
                setParameter(COL_LOWER_DATE, dateRange.getFromDate()).
                setParameter(COL_UPPER_DATE, dateRange.getToDate());
    }

    public static Query getHolidaysForDateRange(
            EntityManager entityManager, DateRange dateRange) {

        return entityManager.createNamedQuery(
                "HolidayRequest.findByDateRange").
                setParameter(COL_LOWER_DATE, dateRange.getFromDate()).
                setParameter(COL_UPPER_DATE, dateRange.getToDate());
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (requestId != null ? requestId.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof HolidayRequest)) {
            return false;
        }
        HolidayRequest other = (HolidayRequest) object;
        if ((this.requestId == null && other.requestId != null) || (this.requestId != null && !this.requestId.equals(other.requestId))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "TimeRecording.tables.HolidayRequest[requestId=" + requestId + "]";
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException ex) {
            Logger.getLogger(HolidayRequest.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    public void copy(HolidayRequest holidayRequest) {

        this.setRequestId(holidayRequest.getRequestId());

        this.setStatus(holidayRequest.getStatus());

        this.setHoursSpent(holidayRequest.getHoursSpent());
        this.setTakenFromFlexi(holidayRequest.getTakenFromFlexi());

        this.setEmployeeId(holidayRequest.getEmployeeId());
        this.setEmployeeComment(holidayRequest.getEmployeeComment());
        this.setAdministratorId(holidayRequest.getAdministratorId());
        this.setAdministratorComment(holidayRequest.getAdministratorComment());

        this.setFirstDayOfHoliday(holidayRequest.getFirstDayOfHoliday());
        this.setFirstDayPart(holidayRequest.getFirstDayPart());
        this.setLastDayOfHoliday(holidayRequest.getLastDayOfHoliday());
        this.setLastDayPart(holidayRequest.getLastDayPart());
    }

    public void copyClipped(HolidayRequest holidayRequest) {

        // Take the basic copy
        copy(holidayRequest);

        // Ensure all of the dates are specified
        if (firstDayOfHoliday == null) {
            firstDayOfHoliday = DaisyChainSystem.getToday().getTime();
        }

        if (firstDayPart == null) {
            firstDayPart = PartOfDay.ALL_DAY;
        }

        if (lastDayOfHoliday == null) {
            lastDayOfHoliday = DaisyChainSystem.getToday().getTime();
        }

        if (lastDayPart == null) {
            lastDayPart = PartOfDay.ALL_DAY;
        }
    }

    public boolean clashesWith(HolidayRequest other) {

        // Assign dates to open-ended holidays

        Date thisFirst = this.firstDayOfHoliday;
        if (thisFirst == null) {
            thisFirst = DaisyChainSystem.AGES_AGO;
        }

        Date thisLast = this.lastDayOfHoliday;
        if (thisLast == null) {
            thisLast = DaisyChainSystem.IN_THE_FUTURE;
        }

        Date otherFirst = other.firstDayOfHoliday;
        if (otherFirst == null) {
            otherFirst = DaisyChainSystem.AGES_AGO;
        }

        Date otherLast = other.lastDayOfHoliday;
        if (otherLast == null) {
            otherLast = DaisyChainSystem.IN_THE_FUTURE;
        }

        // Don't the holidays even touch one another?
        if (otherFirst.after(thisLast) || otherLast.before(thisFirst)) {
            return false;
        }

        // Is there a least a day of overlap?
        if (thisLast.before(otherFirst) || otherLast.before(thisFirst)) {
            return true;
        }

        if (thisLast.equals(otherFirst)) {
            // See if this holiday butts up to the start of the other
            if(this.lastDayPart == PartOfDay.AM &&
                    other.firstDayPart == PartOfDay.PM) {
                return false;
            }
        }

        if (thisFirst.equals(otherLast)) {
            // See if this holiday butts up to the end of the other
            if (this.firstDayPart == PartOfDay.PM &&
                    other.lastDayPart == PartOfDay.AM) {
                return false;
            }
        }

        return true;
    }
}
